vue使用xlxs导入和导出Excel(纯前端导出),xlxl

您所在的位置:网站首页 js 解析xls vue使用xlxs导入和导出Excel(纯前端导出),xlxl

vue使用xlxs导入和导出Excel(纯前端导出),xlxl

2023-04-05 03:51| 来源: 网络整理| 查看: 265

开始前先说一下本次导入导出适用范围,导入:前端解析Excel,拿到数据;导出:调后端接口,拿到数据,直接前端导出(支持多级表头的表格),普通表格支持10万条数据导出,ps:(耗时大概在12s),字段和数据及其多的多级表格也可以导出3万条,ps:(耗时大概在20s),粗略测试过,实际导出耗时和渲染条数得具体看数据量,,超过这个数据量,可以考虑让后端导出了。

先开始说导出,导出目前有几种导出方法,一种是前端生成html表格,然后把表格转base64,然后导出,ps:(这种方式不需要下载插件,而且改样式也方便,直接改生成表格的代码就可以了),但是这种方式有种缺陷,就是导出的Excel不能用office打开,会报错,ps:(wps打开不会报错),还有这种方式生成的的Excel,实际上用xlxs去做导入解析的时候,也是读取不出来的

本人一开始做的导出就是用这种办法搞得,然后前几天需要去用xlxs.js做导入解析Excel的时候,就报错了,各种Google和百度也没找到具体解决的文章说明,,所以自己把这些解决办法记录一下,方便后来人少踩坑,经过我的各种测试和推测,原因可能是因为这种办法导出的Excel实际上不是一个真正格式的Excel,本质上是一个html文件,我把导出的Excel,直接把文件后缀名改为.html后,神奇的发现,打开真的是一个显示一个表格,而正常的Excel改后缀名打开后应该是乱码的

所以上面那种办法就不适用了,排除,附上一个链接

另一种导出,当然是直接后端导出,前端直接调接口下载了,以上两种方式的导出,我就不具体讲怎么做了,百度上一大堆文章。

第三种方法就是用插件去导出了,我目前只使用过xlxs.js,所以只讲这种方式的导出

附上我参考的文章链接:

先安装xlxs和 xlsx-style(修改导出Excel样式需要用到)

cnpm install xlsx --save cnpm install xlsx-style --save

这里引用两个插件会有两个报错,引用xlsx-style的时候

解决办法,一种是直接去改源码,网上大部分解决办法是这种

一种是改配置,建议使用这种,一劳永逸,上面那种办法改源码的,重新安装依赖或者别的小伙伴把代码拉下来跑也会报错,治标不治本

另一种报错,是两个插件默认抛出的插件名都是XLXS,所以引入的时候得改下引入方式

到这里准备工作就做好了,接下来讲一下使用插件的一些考虑,由于我这边的导出基本每个页面都需要用到,还需要区分导出普通表格和多级表头表格,导出的数据,也是调的分页查询的接口,获取前1万条数据导出,所以我这边最方便的是直接把导出功能嵌套进封装好的表格组件里,直接生成el-table,然后把整个表格dom放进xlxs里进行数据转换导出。下面上填写表格的的配置项代码ps:(由于每个人封装的组件都不一样,所以表格的方法可以参数一下,没必要一模一样,但是思路都是一样的)

这里得值提一下,因为我这个表格组件在页面上用到了,在弹框里也用到了,如果id写死的话,每次弹框的导出,根据id去获取dom元素,,每次都会获取到页面上的dom元素,不会获取到弹框里的,所以我这里是给定一个默认id,如果父组件传了id,就把id换了

表格配置项,有几级表头,就一直children递归进去

生成的表格

调用导出方法

导出条件不可能只导出一万条,还会有其它查询参数,所以查询的时候最好带上其它查询参数

//获取10000条数据导出表格 async handleExcel(fileName, param) { let params = { ...param, pageIndex: 1, pageSize: 10000, }; var headLength = 0; //获取表格有几列 var colsLength = 0; //获取表格表头的行数,就是最多是几级表头 //递归获得表头最大层数 function fcHeadLength(header, headerRowLength) { headerRowLength++; header.forEach((item) => { if (item.children && item.children.length > 0) { fcHeadLength(item.children, headerRowLength); } else { if (item.label !== "操作") { headLength++; if (headerRowLength > colsLength) { colsLength = headerRowLength; } } return headLength; } }); } fcHeadLength(this.options.header, 0); const loading = this.$loading({ lock: true, text: "获取数据转换中...", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }); const data = await this.options.listApi.serviceFN(params); if (data.code == 200) { loading.close(); if (data.records === 0) { this.$message({ message: "当前表格数据为空,不能导出!", type: "warning", }); return; } //对一些特殊列的单元格进行处理,比如说启用状态的列 //我页面上显示的是一个开关el-switch,导出成Excel,要把显示的数据转换一下 data.rows.forEach((el) => { this.options.header.forEach((item) => { if (el.hasOwnProperty(item.propName)) { if (item.label == "启用状态") { el[item.propName] == "1" ? (el[item.propName] = "开启") : (el[item.propName] = "关闭"); } } }); }); this.excelTableData = data.rows; //调用导出Excel的方法 this.$nextTick(() => { this.$pubMeth.excelTable( this.hideTableId, //表格id `${fileName}${this.$moment(Date.now()).format("YYYYMMDD")}`, //导出的文件名+时间 headLength, //一共有几列 data.records + colsLength, //一共有几行 colsLength //表头有几行 ); }); } },

以上就是表格的一些处理,可以看一下传给导出Excel方法的具体参数

最重要的导出方法代码来了:

import * as XLSX from "xlsx"; import * as XLSX2 from "xlsx-style"; const publicMethod = { //导出为Excel方法 tableToExcel(tableID, fileName, headLength, colsLength, headColsLength) { console.log(tableID, fileName, headLength, colsLength, headColsLength); // return console.time("导出总耗时"); //先添加表格样式,再下载 var sheet = XLSX.utils.table_to_sheet(document.querySelector(`#${tableID}`)); //将一个table对象转换成一个sheet对象 // console.log(sheet); var arr = [] //获取所有列的名称集合 for (let i = 0; i < headLength; i++) { if (i < 26) { arr.push(String.fromCharCode((65 + i)).toUpperCase()) } else { // console.log(i, String.fromCharCode((65 + (i - 26))).toUpperCase()); arr.push('A' + (String.fromCharCode((65 + (i - 26))).toUpperCase())) } } //第一层循环,循环列 for (let i = 0; i < arr.length; i++) { //判断是否多级表头,多级表头的,计算各列单元格长度 if (headColsLength > 1) { //循环多级表头 for (let j = 0; j < headColsLength; j++) { //只循环存在的表头 if (j < headColsLength && sheet[arr[i] + j]) { //判断是多级表头的单元格,计算单元格长度 if (!sheet[arr[i] + (j + 1)] || !sheet[arr[i] + (j - 1)] || (!sheet[arr[i] + (j - 1)] && !sheet[arr[i] + (j + 1)])) { sheet['!cols'].push({ wch: (sheet[arr[i] + j].v.length) * 2 + 3 }) } } } } else { //不是多级表头直接计算单元格长度 sheet['!cols'].push({ wch: (sheet[arr[i] + '1'].v.length) * 2 + 3 }) } //第二层循环,循环各列的每行数据,添加文字垂直居中 for (let k = 0; k < colsLength; k++) { if (sheet[arr[i] + k]) { sheet[arr[i] + k].s = { alignment: { horizontal: "center", vertical: "center", wrap_text: true } } } } } console.timeEnd("导出总耗时"); downloadExcel(sheet2blob(sheet), `${fileName}.xlsx`); //下载 // document.getElementById('app').removeChild(document.getElementById(tableID)) //每次下载完,删除下载生成的表格 function sheet2blob(sheet, sheetName) { sheetName = sheetName || 'sheet1'; var workbook = { SheetNames: [sheetName], Sheets: {} }; workbook.Sheets[sheetName] = sheet; // 生成excel的配置项 var wopts = { bookType: 'xlsx', // 要生成的文件类型 bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性 type: 'binary' }; var wbout = XLSX2.write(workbook, wopts); var blob = new Blob([s2ab(wbout)], { type: "application/octet-stream" }); // 字符串转ArrayBuffer function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } return blob; } function downloadExcel(url, saveName) { if (typeof url == 'object' && url instanceof Blob) { url = URL.createObjectURL(url); // 创建blob地址 } var aLink = document.createElement('a'); aLink.href = url; aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效 var event; if (window.MouseEvent) event = new MouseEvent('click'); else { event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); } aLink.dispatchEvent(event); } }, excelTable(tableID, fileName, headLength, colsLength, headColsLength) { this.tableToExcel(tableID, fileName, headLength, (colsLength + 1), headColsLength); }, } export default publicMethod

这个方法里面,我觉得最难的是,计算列的宽度,让每列宽度自适应,就是最开始注释最多的那块代码,就是下面这块

计算完列的宽度,然后直接让单元格文字垂直居中就可以了,上面的代码块的代码,可以直接拷贝下来用,到时候用的小伙伴,只需要传参的时候,参数传对了即可。下面来看一下导出成品。

普通表格:

多级表格:

可以看到多级表头的列基本都可以做到自适应了,除非极少数情况下,但是大体上并不影响,ps:(到现在为止我也没找到原因,循环计算的时候,可以判断找到这列并添加成功的,但是不知道为什么会没效果,希望知道的小伙伴可以告诉我!)

下面讲导入解析Excel:

直接用的el-upload,我们看下上传的文件是什么样的

看一下解析出来的数据:

这个导入没什么好多的,直接上代码,

onSubmit(formData, uploadFile) { // 读取表格文件 let that = this; const files = uploadFile; //本地上传的文件 if (files.length { try { const data = ev.target.result; const workbook = XLSX.read(data, { type: "binary", }); const wsname = workbook.SheetNames[0]; //取第一张表 const ws = XLSX.utils.sheet_to_json( workbook.Sheets[wsname] ); //生成json表格内容 console.log(ws, "ws"); //把解析出来的Excel数据,直接转成表格需要的数据 ws.forEach((item) => { that.lists.push({ factoryName: item["工厂"], wokhouseName: item["车间"], }); console.log(that.lists); }); } catch (e) { console.error(e); return false; } }; fileReader.readAsBinaryString(files[0].raw); },

主要方法参考文章:

这里需要注意一下,本地上传的文件,从el-upload传出的是什么样的,如下图:

是一个数组,里面装着一个一个的文件,,我的这个方法,只支持读取解析一个Excel,不支持同时解析多个,但是道理都一样,如果想读取多个Excel的话,循环上面那个解析的方法就好了,稍微改一下就可以了。

end,本文到此结束了,第一次写博客,,花了好几个小时,如果你觉得本文对你有帮助,麻烦给我点个赞,谢谢哦!

追加更新:

几天后,发现导出在IE中用不了,会报错,如下图

然后一开始我以为是直接把dom拿来转换的原因,然后各种Google和百度,,都没找到说xlxs不兼容IE的文章,就在楼主百思不得其解的时候,灵光一现,会不会是js方法不兼容IE,然后一步一步排除,,最后确定了是下载方法不兼容,就是下图这个方法

最后把这个方法改一下

function downloadExcel(url, saveName) { var blob = new Blob([url], { type: "application/octet-stream" }); var a = document.createElement("a"); a.id = "downloadFtsetBtn"; a.style.display = "none"; a.target = "_blank"; document.body.appendChild(a); try { var URL = window.URL || window.webkitURL; a.href = URL.createObjectURL(blob); a.download = saveName; if (typeof navigator.msSaveBlob == "function") { //IE navigator.msSaveBlob(blob, saveName); } a.click(); } catch (e) { console.log(e); } }

人生最幸福的事,是你在遇到一个bug,觉得自己无法解决的时候,又找到了解决方法

追加更新:导入兼容ie

ie浏览器导入报错,如图

原因是ie不支持readAsBinaryString方法

翻阅官方文档,找到解决办法

我们加个判断,做个区分,导入代码如下

onSubmit(formData, uploadFile) { // 读取表格文件 let that = this; const files = uploadFile; //本地上传的文件 if (files.length { try { const data = e.target.result; function fixdata(data) { var o = "", l = 0, w = 10240; for(; l { item["检查开始时间"] = this.$moment( item["检查开始时间"] ).format("YYYY-MM-DD HH:mm:ss"); item["检查结束时间"] = this.$moment( item["检查结束时间"] ).format("YYYY-MM-DD HH:mm:ss"); let obj = { factoryName: item["工厂"], wokhouseName: item["车间"], carTypeCode: item["车型"], checkProject: item["检查项目"], orgName: item["班组"], planStartTime: item["检查开始时间"], planEndTime: item["检查结束时间"], checkFrequenc: item["检查频率"], checkFrequencyType: item["检查频率类型"], planDesc: item["描述"], }; if (this.listOption.listData.length > 0) { this.filterArr([obj]); } else { console.log(555,obj); that.listOption.listData.push(obj); } }); that.$refs.listFilters.handleClose(); } catch (e) { console.error(e); return false; } }; },

到此就可以解决ie的导入了

ps:附上一张图



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3